home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Info-Mac 3
/
Info_Mac_1994-01.iso
/
Development
/
Information
/
Mac Programming Secrets 1.0.1
/
Chapter 06
/
Neat Stuff.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-05-19
|
21KB
|
684 lines
#include "Standard Stuff.h"
#include "Neat Stuff.h"
#include "MouseTracker.h"
const RGBColor kRGBBlack = {0, 0, 0};
const RGBColor kRGBWhite = {0xFFFF, 0xFFFF, 0xFFFF};
Pattern gAntsPattern = {0x1F, 0x3E, 0x7C, 0xF8, 0xF1, 0xE3, 0xC7, 0x8F};
long gLastAntMarch;
const short kMarchTime = 1;
/*******************************************************************************
DoMarchingAnts
This routine is used to do the actual marching of the ants (animate the
selection rectangle). It should be called periodically, like in the main
event loop of the application.
The routine first makes a few checks. It makes sure that we have the
animation turned on, that we have a front window, that an adequate time
has passed since the last time we animated the selection, and that there
is indeed a selection rectangle for the window. If all those condition
hold, we animate the selection rectangle. This is done by shifting all of
the horizontal lines of the pattern downwards by a single line. The line
at the bottom is moved to the top of the pattern. This gives the following
transformation:
Start Result
======== ========
11111000 ---\ 01111100 <--+
11110001 \----> 11111000 |
11100011 ---\ 11110001 |
11000111 \----> 11100011 |
10001111 ---\ 11000111 |
00011111 \----> 10001111 |
00111110 ---\ 00011111 |
01111100 -\ \----> 00111110 |
\ |
\-------------------+
Once we have the new pattern, we redraw the selection rectangle with it.
*******************************************************************************/
void DoMarchingAnts(void)
{
MyWindowPtr theWindow;
Rect antsRect;
short i;
unsigned char lastPart;
theWindow = (MyWindowPtr) FrontWindow();
if (theWindow && theWindow->antsOnTheMarch && (TickCount() > gLastAntMarch + kMarchTime)) {
antsRect = theWindow->selectionRect;
if (!EmptyRect(&antsRect)) {
lastPart = gAntsPattern[7];
for (i = 7; i > 0; --i)
gAntsPattern[i] = gAntsPattern[i-1];
gAntsPattern[0] = lastPart;
PenNormal();
PenPat(gAntsPattern);
SetPort((GrafPtr) theWindow);
FrameRect(&antsRect);
gLastAntMarch = TickCount();
}
}
}
/*******************************************************************************
KillAnts
Turn the selection rectangle animation off. We would want to do this, for
example, if the application is put into the background.
*******************************************************************************/
void KillAnts(MyWindowPtr whichWindow)
{
Rect theRect;
whichWindow->antsOnTheMarch = FALSE;
theRect = whichWindow->selectionRect;
RefreshWindow(whichWindow, &theRect);
}
/*******************************************************************************
StartAnts
Turn the ants on. We would want to call this routine whenever our
application moves from the background into the foreground.
*******************************************************************************/
void StartAnts(MyWindowPtr whichWindow)
{
whichWindow->antsOnTheMarch = TRUE;
}
/*******************************************************************************
DoOpenWindow
Open a MacPaint file and show it in a window. Call Standard File to let
the user select a MacPaint file (such files have a file type of ‘PNTG’).
If the user doesn’t select Cancel, we create a window for the picture,
read the file into an offscreen grafPort, attach the offscreen buffer to
the window, and finally show the window.
*******************************************************************************/
void DoOpenWindow(void)
{
MyWindowPtr newWindow;
SFTypeList types = {'PNTG'};
StandardFileReply reply;
StandardGetFile((FileFilterProcPtr) NIL, 1, types, &reply);
if (reply.sfGood) {
newWindow = CreateBufferedWindow(72*8, 720, 1);
if (newWindow != NIL) {
ReadMacPaint(&reply.sfFile, newWindow->offWorld);
ShowWindow((WindowPtr) newWindow);
}
}
}
/*******************************************************************************
DoCloseWindow
Called when the user selects the “Close” menu item or clicks on the GoAway
box. Disposes of the offscreen grafPort used to buffer the picture, and
then closes the window and disposes of its data structures.
*******************************************************************************/
void DoCloseWindow(WindowPtr theWindow)
{
DisposeGWorld(((MyWindowPtr) theWindow)->offWorld);
DisposeWindow(theWindow);
}
/*******************************************************************************
DoDrawGrowIcon
Draw the grow icon. We do this in such a way that the scrollbar lines are
not drawn. Normally, when the Toolbox routine DrawGrowIcon is called,
vertical and horizontal lines are also drawn indicating where the
scrollbars will be drawn. We don’t have scrollbars, so we don’t want those
lines drawn. We avoid them by setting the clipping region of the window to
include the grow box only. We then call DrawGrowIcon, and restore the old
clipping region before returning.
*******************************************************************************/
void DoDrawGrowIcon(WindowPtr theWindow)
{
Rect iconRect;
RgnHandle oldClip;
SetPort(theWindow);
oldClip = NewRgn();
GetClip(oldClip);
iconRect = theWindow->portRect;
iconRect.top = iconRect.bottom - 15;
iconRect.left = iconRect.right - 15;
ClipRect(&iconRect);
PenNormal();
DrawGrowIcon(theWindow);
SetClip(oldClip);
DisposeRgn(oldClip);
}
/*******************************************************************************
DoActivateWindow
Take care of a window that needs to be activated or deactivated. Normally,
this means removing the highlighting used for the current selection. Since
we _do_ hilite the current selection with Marching Ants, we need to turn
them on or off.
*******************************************************************************/
void DoActivateWindow(WindowPtr theWindow, Boolean activating)
{
if (activating)
StartAnts((MyWindowPtr) theWindow);
else
KillAnts((MyWindowPtr) theWindow);
}
/*******************************************************************************
DoUpdateWindow
Called to update any areas of the window that may have been clobbered by
other windows. We get the bounding rectangle of the update region (which
has been incorporated into the visRgn of the window by a previous call to
BeginUpdate), and pass that into a RefreshWindow routine that does the
actual drawing.
*******************************************************************************/
void DoUpdateWindow(EventRecord *theEvent)
{
MyWindowPtr theWindow;
Rect rectToCopy;
theWindow = (MyWindowPtr) theEvent->message;
rectToCopy = (**(theWindow->windowRecord.port.visRgn)).rgnBBox;
RefreshWindow(theWindow, &rectToCopy);
}
/*******************************************************************************
DoContentClick
Called when the user clicks in the content area of our window. Clicks on
the grow box are detected and handled elsewhere, so we don’t have to worry
about them.
See if the user clicked within the current selection rectangle. If so,
drag the contents of that rectangle around the window. If not, get rid of
the old selection and drag out a new one.
*******************************************************************************/
Rect DoContentClick(EventRecord *theEvent, WindowPtr whichWindow)
{
Point location;
Rect newSelection;
location = theEvent->where;
GlobalToLocal(&location);
if (PointInSelection(whichWindow, location)) {
DragSelection(whichWindow, location);
} else {
KillAnts((MyWindowPtr) whichWindow);
newSelection = SketchNewRect(FALSE);
((MyWindowPtr) whichWindow)->selectionRect = newSelection;
StartAnts((MyWindowPtr) whichWindow);
}
}
/*******************************************************************************
PointInSelection
Utility used to determine if a given point (in the window’s local
coordinates) is within the window’s selection rectangle. This routine is
called from DoContentClick.
*******************************************************************************/
Boolean PointInSelection(WindowPtr whichWindow, Point location)
{
Rect selectionRect;
selectionRect = ((MyWindowPtr) whichWindow)->selectionRect;
return PtInRect(location, &selectionRect);
}
/*******************************************************************************
RefreshWindow
Workhorse that copies a section of the offscreen buffer to the window.
This routine is called from a couple of places, including the update
routines, and the Marching Ant routines.
The pointer to our offscreen buffer is stored after the normal window
record. We retrieve it, get a reference to its PixMap, and lock the bits
so that they don’t move during the call to CopyBits. We then make sure
that the foreground color is set to black, and that the background color
is set to white. If they are set to any other values, then “colorization”
occurs according to Inside Mac V page 71 and Technote #163.
*******************************************************************************/
void RefreshWindow(MyWindowPtr theWindow, Rect *theRect)
{
GrafPtr oldPort;
Rect rectToCopy;
GWorldPtr offWorld;
PixMapHandle offPixMap;
GetPort(&oldPort);
SetPort((GrafPtr) theWindow);
offWorld = theWindow->offWorld;
offPixMap = GetGWorldPixMap(offWorld);
SectRect(theRect, &(**offPixMap).bounds, &rectToCopy);
(void) LockPixels(offPixMap);
RGBForeColor(&kRGBBlack);
RGBBackColor(&kRGBWhite);
CopyBits(&(( (GrafPtr) offWorld)->portBits),
&theWindow->windowRecord.port.portBits,
&rectToCopy, &rectToCopy, srcCopy, NIL);
UnlockPixels(offPixMap);
SetPort(oldPort);
}
/*******************************************************************************
CreateBufferedWindow
Creates a window that’s backed up by an offscreen buffer. We do this by
first defining an extended window record. In the same way that a window
record contains an embedded GrafPort, we define a record with and embedded
WindowRecord. This record includes a pointer to an offscreen buffer and a
rectangle for keeping track of the current selection.
We start by creating an offscreen buffer using NewGWorld (available with
32-bit Color QuickDraw under 6.0.x, and built into 7.0). If that succeeds,
we clear out the buffer with a call to EraseRect, and then start to create
the window. We dynamically allocate the extended window record with NewPtr
and pass the result to GetNewWindow. If we successfully create the window,
we copy in the pointer to the offscreen buffer and initialize the
selection rectangle.
*******************************************************************************/
MyWindowPtr CreateBufferedWindow(short width, short height, short depth)
{
Rect boundsRect;
CTabHandle aColorTable = NIL;
GDHandle aGDevice = NIL;
GWorldFlags flags = 0;
MyWindowPtr newWindow;
GWorldPtr offscreenWorld;
CGrafPtr oldPort;
GDHandle oldDevice;
SetRect(&boundsRect, 0, 0, width, height);
if (NewGWorld(&offscreenWorld, depth, &boundsRect, aColorTable,
aGDevice, flags) != noErr)
return NIL;
GetGWorld(&oldPort, &oldDevice);
SetGWorld(offscreenWorld, NIL);
EraseRect(&qd.thePort->portRect);
SetGWorld(oldPort, oldDevice);
newWindow = (MyWindowPtr) NewPtr(sizeof(MyWindowRecord));
if (newWindow != NIL)
newWindow = (MyWindowPtr) GetNewWindow(kNewWindowID,
(Ptr) newWindow,
(WindowPtr) -1);
if (newWindow == NIL) {
DisposeGWorld(offscreenWorld);
} else {
newWindow->offWorld = offscreenWorld;
newWindow->selectionRect.top = 10;
newWindow->selectionRect.left = 10;
newWindow->selectionRect.bottom = 90;
newWindow->selectionRect.right = 90;
newWindow->antsOnTheMarch = FALSE;
}
return newWindow;
}
/*******************************************************************************
ReadMacPaint
Reads in a MacPaint file according to Technote #86. We open the file, skip
over the first 512 bytes, and read the rest of the file into a buffer.
This buffer now contains the packed MacPaint image. By making successive
calls to UnpackBits, we unpack the image into our offscreen buffer.
*******************************************************************************/
void ReadMacPaint(FSSpec* theFile, GWorldPtr buffer)
{
short refNum;
long fileSize;
short scanline;
Ptr sourceBuffer;
Ptr sourcePtr;
Ptr destPtr;
short rowBytes;
char mmuMode;
if (FSpOpenDF(theFile, fsRdPerm, &refNum) != noErr)
return;
if (GetEOF(refNum, &fileSize) != noErr) {
FSClose(refNum);
return;
}
if (SetFPos(refNum, fsFromStart, 512) != noErr) {
FSClose(refNum);
return;
}
fileSize -= 512;
sourceBuffer = NewPtr(fileSize);
if (sourceBuffer == NIL) {
FSClose(refNum);
return;
}
if (FSRead(refNum, &fileSize, sourceBuffer) != noErr) {
DisposePtr(sourceBuffer);
FSClose(refNum);
return;
}
FSClose(refNum);
sourcePtr = sourceBuffer;
destPtr = GetPixBaseAddr(GetGWorldPixMap(buffer));
rowBytes = (**(GetGWorldPixMap(buffer))).rowBytes & 0x7FFF;
mmuMode = true32b;
SwapMMUMode(&mmuMode);
for (scanline = 0 ; scanline < 720 ; ++scanline) {
UnpackBits(&sourcePtr, &destPtr, 72);
destPtr += (rowBytes - 72);
}
SwapMMUMode(&mmuMode);
DisposePtr(sourceBuffer);
}
/*******************************************************************************
Drag variables - these are used in the routine that drags around the
currently selected part of the picture.
*******************************************************************************/
Point startPoint; /* The point where the user mouse-downed. */
Rect startRect; /* The initial selection rectangle. */
Rect previousRect; /* The last location of the dragged selection. */
Rect currentRect; /* The current location of same. */
GWorldPtr sourceBuffer; /* Buffer that holds the original image. */
GWorldPtr destBuffer; /* Buffer holding modified image. */
WindowPtr gTheWindow; /* Reference to the window we are munging. */
/*******************************************************************************
DragSelection
Called to drag around the current selection. This function is really just
a front end to the real workhorse, TrackMouse(). It sets up some variables
that we’ll need later, and then calls TrackMouse(). TrackMouse() tracks
the mouse, and calls a callback routine we passed to it when needed. This
callback merely has to take care of drawing the selected image in the
right place. When TrackMouse() returns, we make note of the final location
of the dragged image, dispose of our temporary buffer, and return.
*******************************************************************************/
void DragSelection(WindowPtr whichWindow, Point mouse)
{
Point dummyStart;
Point dummyEnd;
gTheWindow = whichWindow;
startPoint = mouse;
startRect = ((MyWindowPtr) whichWindow)->selectionRect;
previousRect = startRect;
destBuffer = ((MyWindowPtr) whichWindow)->offWorld;
sourceBuffer = CloneGWorld(destBuffer);
if (sourceBuffer != NIL) {
TrackMouse(nil, DragSelectionFeedback, nil, &dummyStart, &dummyEnd);
((MyWindowPtr) whichWindow)->selectionRect = currentRect;
DisposeGWorld(sourceBuffer);
}
}
/*******************************************************************************
DragSelectionFeedback
This routine is called by TrackMouse() to provide dragging feedback. Here,
we check to see if the mouse moved. If so, we calculate where the
selection rectangle should be given the current location of the mouse. If
the new selection rectangle is different than the previous rectangle, we
update our working buffer from information stored in our original,
pristine (“I’m not _that_ pristine!” -- Molly Ringwald) buffer.
*******************************************************************************/
void DragSelectionFeedback(Point anchorPoint,
Point nextPoint,
Boolean turnItOn,
Boolean mouseDidMove)
{
#pragma unused (turnItOn)
Rect tempRect;
if (mouseDidMove) {
PointToCurrentRect(nextPoint);
if (!EqualRect(¤tRect, &previousRect)) {
UpdateBuffers();
UnionRect(&previousRect, ¤tRect, &tempRect);
RefreshWindow((MyWindowPtr)gTheWindow, &tempRect);
previousRect = currentRect;
}
}
}
/*******************************************************************************
PointToCurrentRect
This function is used to figure out the where the selected image should be
positioned. When the mousedown occurred, we noted the location of the
mouse click and the original location of the selection rectangle. Using
the current location of the mouse, we figure out the new location of the
selection rectangle. This is done by first figuring the offset of the
current mouse position to the starting position. We then offset the
selection rectangle by the same amount. Once that is done, we make sure
that it’s pinned to the window’s bounds.
*******************************************************************************/
void PointToCurrentRect(Point currentPoint)
{
Point delta;
Rect portRect;
delta = currentPoint;
SubPt(startPoint, &delta);
currentRect = startRect;
portRect = gTheWindow->portRect;
OffsetRect(¤tRect, delta.h, delta.v);
SetPt(&delta, 0, 0);
if (currentRect.left < portRect.left)
delta.h = portRect.left - currentRect.left;
if (currentRect.right > portRect.right)
delta.h = portRect.right - currentRect.right;
if (currentRect.top < portRect.top)
delta.v = portRect.top - currentRect.top;
if (currentRect.bottom > portRect.bottom)
delta.v = portRect.bottom - currentRect.bottom;
OffsetRect(¤tRect, delta.h, delta.v);
}
/*******************************************************************************
UpdateBuffers
During our dragging, we maintain two offscreen buffers. The first holds
the picture as it was before we starting dragging. The second holds the
image as we would like it to appear in the window. This second buffer is
created from the first one. When we first start dragging, the second
buffer is identical to the first. When we need to show the image being
dragged around, we perform the following 3 steps:
1. Using the first buffer, restore the background in the second
buffer where the dragged image used to be. This location is
stored in “previousRect.”
2. Erase the rectangle that shows where the image started from.
This location is stored in “startRect.”
3. Using the first buffer, draw the dragged image where it should
currently be shown. This location is stored in “currentRect.”
*******************************************************************************/
void UpdateBuffers(void)
{
PixMapHandle sourceMap;
PixMapHandle destMap;
CGrafPtr oldPort;
GDHandle oldDevice;
sourceMap = GetGWorldPixMap(sourceBuffer);
destMap = GetGWorldPixMap(destBuffer);
(void) LockPixels(sourceMap);
(void) LockPixels(destMap);
GetGWorld(&oldPort, &oldDevice);
SetGWorld(destBuffer, NIL);
RGBForeColor(&kRGBBlack);
RGBBackColor(&kRGBWhite);
CopyBits(&(((GrafPtr) sourceBuffer)->portBits),
&(((GrafPtr) destBuffer)->portBits),
&previousRect, &previousRect, srcCopy, NIL);
EraseRect(&startRect);
CopyBits(&(((GrafPtr) sourceBuffer)->portBits),
&(((GrafPtr) destBuffer)->portBits),
&startRect, ¤tRect, srcCopy, NIL);
SetGWorld(oldPort, oldDevice);
UnlockPixels(sourceMap);
UnlockPixels(destMap);
}
/*******************************************************************************
CloneGWorld
Given a GWorldPtr, clone it entirely and return the pointer to the new
GWorld. This routine is called when we start dragging and we need to
manipulate two buffers that are initially identical.
The cloning is performed by first getting some of the characteristics of
the GWorld we are cloning. We get the source’s GDeviceHandle, its bit
depth, its color table, its set of gdFlags, and its bounding rectangle.
Using this information, we make a call to NewGWorld to create another
GWorld with the same characteristics. If the call to NewGWorld succeeds,
we copy the source’s image into the clone with a call to CopyBits.
*******************************************************************************/
GWorldPtr CloneGWorld(GWorldPtr sourceGWorld)
{
GDHandle aGDevice;
short depth;
CTabHandle aColorTable;
GWorldFlags flags;
Rect bounds;
GWorldPtr newGWorld;
PixMapHandle sourceMap;
PixMapHandle destMap;
CGrafPtr oldPort;
GDHandle oldDevice;
aGDevice = GetGWorldDevice(sourceGWorld);
depth = (**(**aGDevice).gdPMap).pixelSize;
aColorTable = (**(**aGDevice).gdPMap).pmTable;
HandToHand((Handle*) &aColorTable);
flags = (**aGDevice).gdFlags;
bounds = (**aGDevice).gdRect;
if (NewGWorld(&newGWorld, depth, &bounds, aColorTable, aGDevice, flags) != noErr)
return NIL;
sourceMap = GetGWorldPixMap(sourceGWorld);
destMap = GetGWorldPixMap(newGWorld);
(void) LockPixels(sourceMap);
(void) LockPixels(destMap);
GetGWorld(&oldPort, &oldDevice);
SetGWorld(newGWorld, NIL);
RGBForeColor(&kRGBBlack);
RGBBackColor(&kRGBWhite);
CopyBits(&(((GrafPtr) sourceGWorld)->portBits),
&(((GrafPtr) newGWorld)->portBits),
&bounds, &bounds, srcCopy, NIL);
SetGWorld(oldPort, oldDevice);
UnlockPixels(sourceMap);
UnlockPixels(destMap);
return newGWorld;
}